home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / couchdb / design.py < prev    next >
Text File  |  2009-07-02  |  8KB  |  208 lines

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2008-2009 Christopher Lenz
  4. # All rights reserved.
  5. #
  6. # This software is licensed as described in the file COPYING, which
  7. # you should have received as part of this distribution.
  8.  
  9. """Utility code for managing design documents."""
  10.  
  11. from copy import deepcopy
  12. from inspect import getsource
  13. from itertools import groupby
  14. from operator import attrgetter
  15. from textwrap import dedent
  16. from types import FunctionType
  17.  
  18. __all__ = ['ViewDefinition']
  19. __docformat__ = 'restructuredtext en'
  20.  
  21.  
  22. class ViewDefinition(object):
  23.     r"""Definition of a view stored in a specific design document.
  24.     
  25.     An instance of this class can be used to access the results of the view,
  26.     as well as to keep the view definition in the design document up to date
  27.     with the definition in the application code.
  28.     
  29.     >>> from couchdb import Server
  30.     >>> server = Server('http://localhost:5984/')
  31.     >>> db = server.create('python-tests')
  32.     
  33.     >>> view = ViewDefinition('tests', 'all', '''function(doc) {
  34.     ...     emit(doc._id, null);
  35.     ... }''')
  36.     >>> view.get_doc(db)
  37.  
  38.     The view is not yet stored in the database, in fact, design doc doesn't
  39.     even exist yet. That can be fixed using the `sync` method:
  40.  
  41.     >>> view.sync(db)
  42.  
  43.     >>> design_doc = view.get_doc(db)
  44.     >>> design_doc                                          #doctest: +ELLIPSIS
  45.     <Document '_design/tests'@'...' {...}>
  46.     >>> print design_doc['views']['all']['map']
  47.     function(doc) {
  48.         emit(doc._id, null);
  49.     }
  50.  
  51.     If you use a Python view server, you can also use Python functions instead
  52.     of code embedded in strings:
  53.     
  54.     >>> def my_map(doc):
  55.     ...     yield doc['somekey'], doc['somevalue']
  56.     >>> view = ViewDefinition('test2', 'somename', my_map, language='python')
  57.     >>> view.sync(db)
  58.     >>> design_doc = view.get_doc(db)
  59.     >>> design_doc                                          #doctest: +ELLIPSIS
  60.     <Document '_design/test2'@'...' {...}>
  61.     >>> print design_doc['views']['somename']['map']
  62.     def my_map(doc):
  63.         yield doc['somekey'], doc['somevalue']
  64.     
  65.     Use the static `sync_many()` method to create or update a collection of
  66.     views in the database in an atomic and efficient manner, even across
  67.     different design documents.
  68.  
  69.     >>> del server['python-tests']
  70.     """
  71.  
  72.     def __init__(self, design, name, map_fun, reduce_fun=None,
  73.                  language='javascript', wrapper=None, **defaults):
  74.         """Initialize the view definition.
  75.         
  76.         Note that the code in `map_fun` and `reduce_fun` is automatically
  77.         dedented, that is, any common leading whitespace is removed from each
  78.         line.
  79.         
  80.         :param design: the name of the design document
  81.         :param name: the name of the view
  82.         :param map_fun: the map function code
  83.         :param reduce_fun: the reduce function code (optional)
  84.         :param language: the name of the language used
  85.         :param wrapper: an optional callable that should be used to wrap the
  86.                         result rows
  87.         """
  88.         if design.startswith('_design/'):
  89.             design = design[8:]
  90.         self.design = design
  91.         self.name = name
  92.         if isinstance(map_fun, FunctionType):
  93.             map_fun = _strip_decorators(getsource(map_fun).rstrip())
  94.         self.map_fun = dedent(map_fun.lstrip('\n'))
  95.         if isinstance(reduce_fun, FunctionType):
  96.             reduce_fun = _strip_decorators(getsource(reduce_fun).rstrip())
  97.         if reduce_fun:
  98.             reduce_fun = dedent(reduce_fun.lstrip('\n'))
  99.         self.reduce_fun = reduce_fun
  100.         self.language = language
  101.         self.wrapper = wrapper
  102.         self.defaults = defaults
  103.  
  104.     def __call__(self, db, **options):
  105.         """Execute the view in the given database.
  106.         
  107.         :param db: the `Database` instance
  108.         :param options: optional query string parameters
  109.         :return: the view results
  110.         :rtype: `ViewResults`
  111.         """
  112.         merged_options = self.defaults.copy()
  113.         merged_options.update(options)
  114.         return db.view('/'.join([self.design, self.name]),
  115.                        wrapper=self.wrapper, **merged_options)
  116.  
  117.     def __repr__(self):
  118.         return '<%s %r>' % (type(self).__name__, '/'.join([
  119.             '_design', self.design, '_view', self.name
  120.         ]))
  121.  
  122.     def get_doc(self, db):
  123.         """Retrieve and return the design document corresponding to this view
  124.         definition from the given database.
  125.         
  126.         :param db: the `Database` instance
  127.         :return: a `client.Document` instance, or `None` if the design document
  128.                  does not exist in the database
  129.         :rtype: `Document`
  130.         """
  131.         return db.get('_design/%s' % self.design)
  132.  
  133.     def sync(self, db):
  134.         """Ensure that the view stored in the database matches the view defined
  135.         by this instance.
  136.         
  137.         :param db: the `Database` instance
  138.         """
  139.         type(self).sync_many(db, [self])
  140.  
  141.     @staticmethod
  142.     def sync_many(db, views, remove_missing=False, callback=None):
  143.         """Ensure that the views stored in the database that correspond to a
  144.         given list of `ViewDefinition` instances match the code defined in
  145.         those instances.
  146.         
  147.         This function might update more than one design document. This is done
  148.         using the CouchDB bulk update feature to ensure atomicity of the
  149.         operation.
  150.         
  151.         :param db: the `Database` instance
  152.         :param views: a sequence of `ViewDefinition` instances
  153.         :param remove_missing: whether views found in a design document that
  154.                                are not found in the list of `ViewDefinition`
  155.                                instances should be removed
  156.         :param callback: a callback function that is invoked when a design
  157.                          document gets updated; the callback gets passed the
  158.                          design document as only parameter, before that doc
  159.                          has actually been saved back to the database
  160.         """
  161.         docs = []
  162.  
  163.         for design, views in groupby(views, key=attrgetter('design')):
  164.             doc_id = '_design/%s' % design
  165.             doc = db.get(doc_id, {'_id': doc_id})
  166.             orig_doc = deepcopy(doc)
  167.             languages = set()
  168.  
  169.             missing = list(doc.get('views', {}).keys())
  170.             for view in views:
  171.                 funcs = {'map': view.map_fun}
  172.                 if view.reduce_fun:
  173.                     funcs['reduce'] = view.reduce_fun
  174.                 doc.setdefault('views', {})[view.name] = funcs
  175.                 languages.add(view.language)
  176.                 if view.name in missing:
  177.                     missing.remove(view.name)
  178.  
  179.             if remove_missing and missing:
  180.                 for name in missing:
  181.                     del doc['views'][name]
  182.             elif missing and 'language' in doc:
  183.                 languages.add(doc['language'])
  184.  
  185.             if len(languages) > 1:
  186.                 raise ValueError('Found different language views in one '
  187.                                  'design document (%r)', list(languages))
  188.             doc['language'] = list(languages)[0]
  189.  
  190.             if doc != orig_doc:
  191.                 if callback is not None:
  192.                     callback(doc)
  193.                 docs.append(doc)
  194.  
  195.         db.update(docs)
  196.  
  197.  
  198. def _strip_decorators(code):
  199.     retval = []
  200.     beginning = True
  201.     for line in code.splitlines():
  202.         if beginning and not line.isspace():
  203.             if line.lstrip().startswith('@'):
  204.                 continue
  205.             beginning = False
  206.         retval.append(line)
  207.     return '\n'.join(retval)
  208.